/**
YASTACS universum data types.

Authors: Kalman Kiss, kiskami@freemail.hu

License: GNU General Public License

	YASTACS game
    Copyright (C) 2012 Kalman Kiss, kiskami@freemail.hu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
	Contact author at: kiskami@freemail.hu or
					Hungary, 8900 Zalaegerszeg
					Kakukkfu u 4.
*/
module ugen.UniversumData;

import std.stdio;
import std.conv;
import std.string;

import util.json;

class Universum {
	/// sector species
	enum Species { SP_NO, SP_HUMAN, SP_MANDULIN, SP_CATOV, SP_HYDROZ, SP_KLOUDER }
	/// startypes
	enum StarTypes { ST_PROTO, ST_MAINSEQ, ST_REDGIANT, ST_WHITEDWARF, ST_NEUTRON }

	/// planettypes
	enum PlanetTypes { PT_GASGIANT, PT_ICEGIANT, PT_TERRESTRIAL }

	/// cloudtypes
	enum CloudTypes { CT_EMISSION, CT_PLANETARY, CT_PROTO, CT_SUPER }

	/// create empty universum
	this(uint xsize_, uint ysize_, uint zsize_) {
		xsize = xsize_; ysize = ysize_; zsize = zsize_;
	}

	uint[3] getSizes() { return [xsize, ysize, zsize]; }

	Sector addSector(uint x, uint y, uint z, string name_, Species species) {
		return addSector(new Sector(x, y, z, name_, species));
	}

	Sector addSector(Sector s) {
		Sector res = null;
		if(!checkSector(s.getX(),s.getY(),s.getZ())) {
			sectors[getSectorKey(s.getX(),s.getY(),s.getZ())] = s;
			res = s;
		} else
			debug writeln("Warning! Discarding sector (", s.getX(), ",", s.getY(), ",", s.getZ(), ")" 
						  , "because it already exists!");
		return res;
	}

	/// export funkció analizációhoz
	string exportAsGNUPlot() {
		string res;
		foreach(string k, Sector s ; sectors) { 
			res ~= format("%s %s %s %s bh %s ssn %s\n"
						  , to!string(s.getX()), to!string(s.getY()), to!string(s.getZ())
						  , s.getName()
						  , (s.hasBlackHole()?"yes":"no")
						  , to!string(s.getSolSysNum()));
		}
		return res;
	}

	string toJson(bool pretty=false) {
		JSONObject root = new JSONObject();
		root["xsize"] = new JSONNumber(xsize);		
		root["ysize"] = new JSONNumber(ysize);		
		root["zsize"] = new JSONNumber(zsize);		

		JSONArray sectors_json = new JSONArray();
		foreach(string k, Sector s ; sectors) { 
			sectors_json ~= s.toJson();
		}
		root["sectors"] = sectors_json;

		return pretty?root.toPrettyString:root.toString;
	}

	/// create universum data from JSON
	static Universum createFromJson(string json) {
		auto jo = readJSON(json);

		uint xsize = to!uint(jo["xsize"].toJSONNumber().get());
		uint ysize = to!uint(jo["ysize"].toJSONNumber().get());		
		uint zsize = to!uint(jo["zsize"].toJSONNumber().get());

		auto u = new Universum(xsize, ysize, zsize);

		auto secs = jo["sectors"].toJSONArray();
		foreach(JSONType o ; secs) {
			auto sector = Sector.fromJson(o.toJSONObject());
			u.addSector(sector);
		}

		return u;
	}

	Sector sector(string name) {
		if(name in sectors) return sectors[name];
		return null;
	}

	string[] sectorNames() {
		return sectors.keys;
	}

private:

	uint xsize; uint ysize; uint zsize;

	Sector[string] sectors;

	bool checkSector(uint x, uint y, uint z) {
		return (getSectorKey(x, y, z) in sectors) !is null;
	}

	string getSectorKey(uint x, uint y, uint z) {
		return "Sector_" ~ to!string(x) ~ "_" ~ to!string(y) ~ "_" ~ to!string(z);
	}

};


class Sector {
public:
	this(uint x_, uint y_, uint z_, string name_, Universum.Species owner_) {
		x = x_; y = y_; z = z_;
		name = name_;
		owner = owner_;
	}

	void addBlackHole(BlackHole bh_) {
		bh = bh_;
	}

	void addSolSys(SolSystem ss_) { 
		solsyss ~= ss_;
	}

	void addAsteroidBelt(AsteroidBelt ab_) {
		asteroidbelts ~= ab_;
	}

	void addCloud(Cloud c_) {
		clouds ~= c_;
	}

	uint getX() { return x; }
	uint getY() { return y; }
	uint getZ() { return z; }

	string getName() { return name; }

	bool hasBlackHole() { return bh !is null; }

	size_t getSolSysNum() { return solsyss.length; }

	JSONObject toJson() {
		JSONObject res = new JSONObject(); 

		res["name"] = new JSONString(name);
		res["x"] = new JSONNumber(x);
		res["y"] = new JSONNumber(y);
		res["z"] = new JSONNumber(z);

		res["owner"] = new JSONNumber(owner);

		JSONArray solsyss_json = new JSONArray();
		foreach(SolSystem s ; solsyss) { 
			solsyss_json ~= s.toJson();
		}
		res["solsyss"] = solsyss_json;

		if(bh) {
			res["blackhole"] = bh.toJson();
		}

		JSONArray clouds_json = new JSONArray();
		foreach(Cloud s ; clouds) { 
			clouds_json ~= s.toJson();
		}
		res["clouds"] = clouds_json;

		JSONArray asbelts_json = new JSONArray();
		foreach(AsteroidBelt s ; asteroidbelts) { 
			asbelts_json ~= s.toJson();
		}
		res["asteroidbelts"] = asbelts_json;

		return res;
	}

	static Sector fromJson(JSONObject s) {

		auto res = new Sector(to!uint(s["x"].toJSONNumber().get())
						, to!uint(s["y"].toJSONNumber().get())
						, to!uint(s["z"].toJSONNumber().get())
						, s["name"].toJSONString().get()
						, cast(Universum.Species)(to!uint(s["owner"].toJSONNumber().get())));

		auto solsyss = s["solsyss"].toJSONArray();
		foreach(JSONType o ; solsyss) {
			auto solsys = SolSystem.fromJson(o.toJSONObject());
			res.addSolSys(solsys);
		}

		if(s["blackhole"]) {
			res.addBlackHole(BlackHole.fromJson(s["blackhole"].toJSONObject()));
		}

		auto clouds = s["clouds"].toJSONArray();
		foreach(JSONType o ; clouds) {
			auto cloud = Cloud.fromJson(o.toJSONObject());
			res.addCloud(cloud);
		}

		auto abs = s["asteroidbelts"].toJSONArray();
		foreach(JSONType o ; abs) {
			auto ab = AsteroidBelt.fromJson(o.toJSONObject());
			res.addAsteroidBelt(ab);
		}
		return res;
	}

private:
		uint x, y, z;
		string name;
	
		Universum.Species owner;

		SolSystem[] solsyss;
		
		BlackHole bh;
		
		Cloud[] clouds;
		
		AsteroidBelt[] asteroidbelts;
};


class SolSystem {
public:
	this(string name_) {
		name = name_;
	}

	string getName() { return name; }

	void addStar(Star star) {
		stars ~= star;
	}

	void addPlanet(Planet planet) {
		planets ~= planet;
	}

	JSONObject toJson() {
		JSONObject res = new JSONObject(); 

		res["name"] = new JSONString(name);

		JSONArray stars_json = new JSONArray();
		foreach(Star s ; stars) { 
			stars_json ~= s.toJson();
		}
		res["stars"] = stars_json;

		JSONArray planets_json = new JSONArray();
		foreach(Planet s ; planets) { 
			planets_json ~= s.toJson();
		}
		res["planets"] = planets_json;

		return res;
	}

	static SolSystem fromJson(JSONObject s) {

		auto res = new SolSystem(s["name"].toJSONString().get());

		auto ss = s["stars"].toJSONArray();
		foreach(JSONType o ; ss) {
			auto so = Star.fromJson(o.toJSONObject());
			res.addStar(so);
		}

		auto ps = s["planets"].toJSONArray();
		foreach(JSONType o ; ps) {
			auto po = Planet.fromJson(o.toJSONObject());
			res.addPlanet(po);
		}

		return res;
	}

private:
	string name;

	Star[] stars;

	Planet[] planets;
};

/// Black Hole data
class BlackHole {
public:	
	this(bool rotating_, string name_) {
		rotating = rotating_;
		name = name_;
	}

	string getName() { return name; }

	JSONObject toJson() {
		JSONObject res = new JSONObject(); 
		res["name"] = new JSONString(name);
		res["rotating"] = new JSONBoolean(rotating);
		return res;
	}

	static BlackHole fromJson(JSONObject bh) {
		return new BlackHole(bh["rotating"].toJSONBoolean().get()
							,bh["name"].toJSONString().get());
	}

private:
	bool rotating;
	string name;
};

class Cloud {
public:
	this(string name_, Universum.CloudTypes type_) {
		name = name_;
		type = type_;
	}

	string getName() { return name; }

	JSONObject toJson() {
		JSONObject res = new JSONObject(); 
		res["name"] = new JSONString(name);
		res["type"] = new JSONNumber(type);
		return res;
	}

	static Cloud fromJson(JSONObject s) {
		return new Cloud(s["name"].toJSONString().get()
						, cast(Universum.CloudTypes)(to!uint(s["type"].toJSONNumber().get())));
	}
private:
	string name;
	Universum.CloudTypes type;
};

class AsteroidBelt {
public:
	this(string name_) {
		name = name_;
	}

	string getName() { return name; }

	JSONObject toJson() {
		JSONObject res = new JSONObject(); 
		res["name"] = new JSONString(name);
		return res;
	}

	static AsteroidBelt fromJson(JSONObject s) {
		return new AsteroidBelt(s["name"].toJSONString().get());
	}

private:

	string name;
};


class Star {
public:
	this(string name_, Universum.StarTypes type_) {
		name = name_;
		type = type_;
	}

	string getName() { return name; }

	JSONObject toJson() {
		JSONObject res = new JSONObject(); 
		res["name"] = new JSONString(name);
		res["type"] = new JSONNumber(type);
		return res;
	}

	static Star fromJson(JSONObject s) {
		return new Star(s["name"].toJSONString().get()
						 , cast(Universum.StarTypes)(to!uint(s["type"].toJSONNumber().get())));
	}

private:

	string name;
	Universum.StarTypes type;
};

class Planet {
public:
	this(string name_, Universum.PlanetTypes type_) {
		name = name_;
		type = type_;
	}

	string getName() { return name; }

	JSONObject toJson() {
		JSONObject res = new JSONObject(); 
		res["name"] = new JSONString(name);
		res["type"] = new JSONNumber(type);
		return res;
	}

	static Planet fromJson(JSONObject s) {
		return new Planet(s["name"].toJSONString().get()
						, cast(Universum.PlanetTypes)(to!uint(s["type"].toJSONNumber().get())));

	}

private:

	string name;
	Universum.PlanetTypes type;
};
